// TODO: Make a queue of modules that need to be started or stopped so it's not done in the middle of one of these loops.
#include <Kharlia/Core/Common.h>
#include <Kharlia/Core/App.h>
#include <Kharlia/Core/ObjectUtil.h>
#include <Kharlia/Core/Subsystem.h>
#include <Kharlia/Core/Tasker.h>
#include <Kharlia/Core/Logger.h>
#include <Kharlia/Core/IO.h>
#include <iostream>
#include <ctime>

#define DEFAULT_EXIT_WAIT_TIME 2.0
#define DEFAULT_SLEEP_TIME 1
#define DEFAULT_TICK_RATE 0
#define SECOND_DISPATCH_ENABLED 0

namespace kharlia { namespace core {

// Private declaration for function in Main.cpp
void _SetMainLogger(const shared_ptr<FLogger>& logger);

// -------------------------
// CSubsystem
// -------------------------

KH_NO_GC(CSubsystem)

// -------------------------
// CApp
// -------------------------

CApp* CApp::msInstance = NULL;

CApp::CApp(PyObject* self):
    CObject(self),
    mExitRequested(false),
    mExitLoop(false),
    mIsRunning(false),
    mHasRemoves(false),
    mThreadSleepTime(DEFAULT_SLEEP_TIME),
    mTickRate(DEFAULT_TICK_RATE),
    mExitWaitTime(DEFAULT_EXIT_WAIT_TIME),
    mDeltaTime(0.0),
    mLastTickTime(0.0),
    mIOManager()
{
    if (msInstance)
        throw RuntimeError("instance already exists");
    msInstance = this;
}

CApp::~CApp() {
    assert(msInstance);
    msInstance = NULL;
}

void CApp::_RequestExit() {
    GetMainLogger()->Info("exit has been requested");
    mExitRequested = true;
}

void CApp::ForceExit() {
    mExitLoop = true;
}

void CApp::_Run() {
    // Initialization
    try {
        assert(mIOManager);
        _SetMainLogger(FLogger::GetLogger("core"));
        mIsRunning = true;
        // Main Loop
        double ExitTime = 0.0;
        while (!mExitLoop) {
            Tick();
            if (mExitRequested) {
                if (ExitTime == 0.0) {
                    ExitTime = GetRealTime() + mExitWaitTime;
                }
                else if (GetRealTime() >= ExitTime) {
                    mExitLoop = true;
                }
            }
        }
        // Exiting
        mIsRunning = false;
        mSubsystemsToAdd.clear();
        mSubsystems.clear();
    }
    catch (...) {
        mIsRunning = false;
        mSubsystemsToAdd.clear();
        mSubsystems.clear();
        throw;
    }
}

void CApp::Tick() {
    mTickCount = GetRealTickCount();
    mTime = double(mTickCount) / KH_TICKS_PER_SECOND;
    mDeltaTime = float(mTime - mLastTickTime);
    // Dispatch IO tasklets
    mIOManager->Dispatch(true);
    // Tick subsystems
    try {
        TickSubsystems(mDeltaTime);
    }
    catch (error_already_set&) {
        PyErr_Clear();
        GetMainLogger()->Critical(
            "caught python exception during subsystem ticking, exiting");
        RequestExit();
    }
    catch (...) {
        GetMainLogger()->Critical(
            "caught native exception during subsystem ticking, exiting");
        RequestExit();
    }
    // Record last tick time
    mLastTickTime = mTime;
    // Calculate sleep time if there is a target FPS
    if (mTickRate != 0) {
        double loopTime = (GetRealTime() - mTime);
        double targetTime = 1.0 / double(mTickRate);
        mThreadSleepTime = uint16(((loopTime < targetTime)? (targetTime - loopTime): 0.001) * 1000);
        //ThreadSleepTime = (loopTime < targetTime)? ((targetTime - loopTime) + 1): 2;
    }
    // Release the GIL and sleep
    Py_BEGIN_ALLOW_THREADS
    SleepThread(mThreadSleepTime);
    Py_END_ALLOW_THREADS
}

void CApp::TickSubsystems(float deltaTime) {
    if (mHasRemoves) {
        for (int i = mSubsystems.size() - 1; i >= 0; i--) {
            if (mSubsystems[i].Remove) {
                mSubsystems.erase(mSubsystems.begin() + i);
            }
        }
        mHasRemoves = false;
    }
    if (mSubsystemsToAdd.size() > 0) {
        BOOST_FOREACH(FSubsystemInfo& si, mSubsystemsToAdd) {
            if (mSubsystems.size() == 0) {
                mSubsystems.push_back(si);
            }
            else {
                for (int i = mSubsystems.size() - 1; i >= 0; i--) {
                    if (mSubsystems[i].Index <= si.Index) {
                        mSubsystems.insert(mSubsystems.begin() + i + 1, si);
                        break;
                    }
                }
            }
        }
        mSubsystemsToAdd.clear();
    }
    for (uint i = 0; i < mSubsystems.size(); i++) {
        mSubsystems[i].Ptr->Tick(deltaTime);
    }
}

void CApp::AddSubsystem(sint16 index, shared_ptr<CSubsystem> p) {
    mSubsystemsToAdd.push_back(FSubsystemInfo(index, p));
}

void CApp::RemoveSubsystem(shared_ptr<CSubsystem> p) {
    BOOST_FOREACH(FSubsystemInfo& si, mSubsystems) {
        if (si.Ptr == p) {
            si.Remove = true;
            mHasRemoves = true;
        }
    }
}

list CApp::GetSubsystemList() {
    list output;
    for (uint i = 0; i < mSubsystems.size(); i++) {
        output.append(py::make_tuple(mSubsystems[i].Index, mSubsystems[i].Ptr));
    }
    return output;
}

KH_BEGIN_GC(CApp)
    KH_GC_SHARED_PTR(thisx->mIOManager)
    KH_GC_SHARED_PTR(thisx->mTasker)
    BOOST_FOREACH(FSubsystemInfo& s, thisx->mSubsystems) {
        KH_GC_SHARED_PTR(s.Ptr)
    }
    BOOST_FOREACH(FSubsystemInfo& s, thisx->mSubsystemsToAdd) {
        KH_GC_SHARED_PTR(s.Ptr)
    }
KH_END_GC()

void _InitAppBindings() {
    using namespace py;

    class_
        <CSubsystem, shared_ptr<CSubsystem>, bases<CObject>, noncopyable>
        ("CSubsystemBase", init<>())
        .def("Tick", pure_virtual(&CSubsystem::Tick))
        ;

    KH_IMPL_GC(CSubsystem);

    _InitTaskerBindings();

    PyDoc_STRVAR(doc_class, "Acts as the kernel of an application. Manages the main loop, "
                            "stackless tasklets, configuration files, and services.");
    PyDoc_STRVAR(doc_link_subsystem, "Links a tickable to the main loop to be ticked on every "
                                     "iteration of the main loop after tasklets have been run.");
    PyDoc_STRVAR(doc_unlink_subsystem, "Removes a tickable from the main loop.");
    PyDoc_STRVAR(doc_timeout, "The amount of time the tasklet runner should run in milliseconds "
                              "until returning. Only returns during a tasklet switch.");
    PyDoc_STRVAR(doc_sleep_time, "The amount of time in miliseconds that the main loop will sleep "
                                 "and release The GIL. Is set automatically if target FPS is not "
                                 "zero.");
    PyDoc_STRVAR(doc_target_fps, "The target FPS. Determines how long the main loop sleeps. If "
                                 "it is not zero, it will calculate the sleep time during each "
                                 "tick.");
    PyDoc_STRVAR(doc_exit_wait_time, "The time in miliseconds that the main loop waits before exiting "
                                     "for tasklets to stop naturally and exit events to be broadcasted. "
                                     "Default is 2000 miliseconds.");

    class_
        <CApp, shared_ptr<CApp>, bases<CObject>, boost::noncopyable>
        ("CAppBase", doc_class, init<>())
        .def("AddSubsystem", &CApp::AddSubsystem, doc_link_subsystem)
        .def("RemoveSubsystem", &CApp::RemoveSubsystem, doc_unlink_subsystem)
        .def("Run", &CApp::_Run)
        .def("Tick", &CApp::Tick)
        .def("RequestExit", &CApp::_RequestExit)
        .def("ForceExit", &CApp::ForceExit)
        .def("GetSubsystemList", &CApp::GetSubsystemList)
        //.def("GetSubsystem", &CApp::GetSubsystem)
        .def_readonly("Subsystems", &CApp::mSubsystems)
        .def_readonly("IsRunning", &CApp::mIsRunning)
        .def_readonly("ExitRequested", &CApp::mExitRequested)
        .def_readonly("Tasker", &CApp::mTasker)
        .def_readwrite("_Tasker", &CApp::mTasker)
        .def_readonly("IOManager", &CApp::mIOManager)
        .def_readwrite("_IOManager", &CApp::mIOManager)
        .add_property("ThreadSleepTime", &CApp::GetThreadSleepTime, &CApp::SetThreadSleepTime, doc_sleep_time)
        .add_property("TickRate", &CApp::GetTickRate, &CApp::SetTickRate, doc_target_fps)
        .add_property("ExitWaitTime", &CApp::GetExitWaitTime, &CApp::SetExitWaitTime, doc_exit_wait_time)
        .add_property("DeltaTime", &CApp::GetDeltaTime)
        .add_property("Time", &CApp::GetTime)
        .add_property("TickCount", &CApp::GetTickCount)
        .def("GetInstance", &CApp::GetInstance).staticmethod("GetInstance")
        ;

    KH_IMPL_GC(CApp);

    def("GetRealTime", &GetRealTime);
    def("GetRealTickCount", &GetRealTickCount);
}

}} //end namespace